﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using MobMentality.GraphUtil;

namespace MobMentality.Map
{
	/*
	 * Holds Navigation information about the map
	 */ 
	class MapNavigate : IMapNavigate
	{
		private Dictionary<string, string> navigationTypeMap;
		private string[,] navigationType;
		private Graph<Point> navmeshGraph; //navmesh graph using tiles as meshes
		private int rowHeight;
		private int columnWidth;
		private int numRows;
		private int numColumns;
		private Dictionary<Point, float> controlPoints;

		private const int captureTime = 5 * 60; //60 fps -> 5 seconds

		public int NumRows
		{
			get { return numRows; }
		}

		public int NumColumns
		{
			get { return numColumns; }
		}

		public int TileHeight
		{
			get
			{
				return rowHeight;
			}
		}

		public int TileWidth
		{
			get
			{
				return columnWidth;
			}
		}

		/*INITIALIZATION FUNCTIONS*/
		public MapNavigate(int numRows, int numColumns, int rowHeight, int columnWidth, Dictionary<string,string> navigationTypeMap)
		{
			this.navigationTypeMap = navigationTypeMap;
			navigationType = new string[numRows, numColumns];
			controlPoints = new Dictionary<Point, float>();

			navmeshGraph = new Graph<Point>();

			this.numColumns = numColumns;
			this.numRows = numRows;

			this.rowHeight = rowHeight;
			this.columnWidth = columnWidth;
		}

		/*do this in the beginning*/
		public void AddTile(int row, int column, string tileType)
		{
			this.navigationType[row, column] = navigationTypeMap[tileType];
			if (this.navigationType[row, column] == "control point")
			{
				controlPoints.Add(new Point(column, row), 0);
			}
		}

		/*NAVMESH FUNCTIONS*/

		/*Only need to do this once*/
		public void ConstructNavmesh()
		{
			//cost to travel from one node to a neighbor
			const float cost = 1;

			/*Add Nodes*/
			for (int row = 0; row <= numRows; row++)
			{
				for (int column = 0; column <= numColumns; column++)
				{
					navmeshGraph.AddNode(new Point(column, row));
				}
			}

			/*Add Edges*/
			for (int row = 0; row < numRows; row++)
			{
				for (int column = 0; column < numColumns; column++)
				{
					Point current = new Point(column, row);
					
					//Top
					if (row - 1 >= 0)
					{
						Point top = new Point(current.X, current.Y-1);
						if(isNavigable(top))
						{
							navmeshGraph.AddEdge(current, top, cost);
						}
					}

					//Bottom
					if (row + 1 < numRows)
					{
						Point bottom = new Point(current.X, current.Y + 1);
						if (isNavigable(bottom))
						{
							navmeshGraph.AddEdge(current, bottom, cost);
						}
					}

					//Left
					if (column - 1 >= 0)
					{
						Point left = new Point(current.X - 1, current.Y);
						if (isNavigable(left))
						{
							navmeshGraph.AddEdge(current, left, cost);
						}
					}

					//Right
					if (column + 1 < numColumns)
					{
						Point right = new Point(current.X + 1, current.Y);
						if (isNavigable(right))
						{
							navmeshGraph.AddEdge(current, right, cost);
						}
					}
				}
			}
		}

		/*AStar Functions*/
		public Point nextAStarTile(Point start, Point end, Graph<Point>.distanceHeuristic distHeuristic)
		{
			GraphNode<Point> nextNode = navmeshGraph.AStarNextNode(start, end, distHeuristic);
			if(nextNode == null)
			{
				return new Point(-1, -1);
			}
			else
			{
				return nextNode.Value;
			}
		}

		public int AStarDistance(Point start, Point end, Graph<Point>.distanceHeuristic distHeuristic)
		{
			return navmeshGraph.AStarDistance(start, end, distHeuristic);
		}

		/*BFS Function*/
		public Point NearestDistraction(Point start)
		{
			if (navigationType[start.X, start.Y] == "distraction")
			{
				//lol you're already there
				return start;
			}

			Queue<Point> open = new Queue<Point>();
			List<Point> closed = new List<Point>();

			open.Enqueue(start);

			while (open.Count > 0)
			{
				Point current = open.Dequeue();
				closed.Add(current);

				Point[] neighbors = new Point[4];
				neighbors[0] = new Point(current.X - 1, current.Y);
				neighbors[1] = new Point(current.X + 1, current.Y);
				neighbors[2] = new Point(current.X, current.Y - 1);
				neighbors[3] = new Point(current.X, current.Y + 1);

				foreach (Point neighbor in neighbors)
				{
					if (isNavigable(neighbor) && closed.Count(p => p.X == neighbor.X && p.Y == neighbor.Y) == 0)
					{
						if (navigationType[neighbor.X, neighbor.Y] == "distraction")
						{
							return neighbor;
						}
						open.Enqueue(neighbor);
					}
				}
			}

			return new Point(-1, -1);
		}

		/*TILE FUNCTIONS*/
		public string NavigationType(int row, int column)
		{
			return navigationType[row, column];
		}

		public string NavigationType(Microsoft.Xna.Framework.Vector2 Camera, int screenX, int screenY)
		{
			float mapX = Camera.X + screenX;
			float mapY = Camera.Y + screenY;
			int column = (int)(mapX / columnWidth);
			int row = (int)(mapY / rowHeight);

			return navigationType[row, column];
		}

		/*REMEMBER: Y is ROWS, X is COLUMNS*/
		public Point GetTile(Vector2 mapPosition)
		{
			return new Point((int)(mapPosition.X / columnWidth),
								(int)(mapPosition.Y / rowHeight));
		}

		public Vector2 GetMapPosition(Point tile)
		{
			return new Vector2(tile.X * columnWidth,
								tile.Y * rowHeight);
		}

		public Vector2 GetMapPosition(Point tile, Vector2 offest)
		{
			return new Vector2(tile.X * columnWidth + offest.X,
								tile.Y * rowHeight + offest.Y);
		}

		public bool isNavigable(Point tile)
		{
			//off the map is definitely not navigable
			if (tile.X < 0 || tile.X >= numColumns || tile.Y < 0 || tile.Y >= numRows)
			{
				return false;
			}

			//this may be a little simplified right now
			return (navigationType[tile.Y, tile.X] != "obstacle");
		}

		/*CAPTURE POINT FUNCTIONS*/
		public bool isCapturePoint(Point tile)
		{
			//off the map is definitely not navigable
			if (tile.X < 0 || tile.X >= numColumns || tile.Y < 0 || tile.Y >= numRows)
			{
				return false;
			}

			//this may be a little simplified right now
			return (navigationType[tile.Y, tile.X] == "control point");
		}

		/*
		public void update()
		{
			for (int row = 0; row < numRows; row++)
			{
				for (int col = 0; col < numColumns; col++)
				{
					controlPoints[new Point(col, row)] -= 1;
				}
			}
		}
		*/

		public void Capture(Point tile)
		{
			//off the map is definitely not navigable
			if (tile.X < 0 || tile.X >= numColumns || tile.Y < 0 || tile.Y >= numRows)
			{
				return;
			}

			if (isCapturePoint(tile))
			{
				controlPoints[tile] += 1;
			}
		}

		public bool CheckRiotControlpointEndgame()
		{
			foreach (Point controlPoint in controlPoints.Keys)
			{
				if (controlPoints[controlPoint] < captureTime)
				{
					return false;
				}
			}

			return true;
		}

		public List<Point> GetCapturedTiles()
		{
			IEnumerable<KeyValuePair<Point, float>> controlledPoints = controlPoints.Where(pair => (pair.Value > captureTime));
			List<Point> result = new List<Point>();

			foreach (KeyValuePair<Point, float> controlPoint in controlledPoints)
			{
				result.Add(controlPoint.Key);
			}

			return result;
		}
	}
}
